'    Copyright 2008 Jan Molnar <jan.molnar@abds.de>
'
'    This file is part of JCQ.

'    JCQ is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.

'    JCQ is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with JCQ. If not, see <http://www.gnu.org/licenses/>.

Imports System.Linq
Imports System.Xml

Public Class DefaultReferenceFormatter
    Implements IReferenceFormatter

    Private ReadOnly _Serializer As ISerializer
    Private ReadOnly _ReferenceType As Type
    Private ReadOnly _PropertySerializationActions As List(Of IPropertySerializationAction)
    Private ReadOnly _CachedProperites As Dictionary(Of String, PropertyDescriptor)

    Protected Sub New(ByVal parent As ISerializer, ByVal type As Type, ByVal createPropertyCache As Boolean, ByVal createSerializationActions As Boolean)
        _Serializer = parent
        _ReferenceType = type

        Dim propertyDescriptors As List(Of PropertyDescriptor)

        If createPropertyCache Or createSerializationActions Then
            propertyDescriptors = (From prop In type.GetProperties() Select New PropertyDescriptor(prop)).ToList
            propertyDescriptors.TrimExcess()
        End If

        If createPropertyCache Then
            _CachedProperites = propertyDescriptors.ToDictionary(Function(prop) prop.PropertyName)
        End If

        If createSerializationActions Then
            _PropertySerializationActions = New List(Of IPropertySerializationAction)

            For Each descriptor As PropertyDescriptor In propertyDescriptors
                If Not descriptor.IsReadOnly Then
                    If descriptor.IsValueType Then
                        Dim formatter As IValueFormatter = Serializer.GetValueFormatter(descriptor.PropertyType)

                        _PropertySerializationActions.Add(New ValueSerializationAction(descriptor, formatter))
                    ElseIf descriptor.IsString Then
                        _PropertySerializationActions.Add(New StringSerializationAction(descriptor))
                    Else
                        _PropertySerializationActions.Add(New ReferenceSerializationAction(descriptor, Serializer))
                    End If
                ElseIf descriptor.IsIListDescendant Then
                    _PropertySerializationActions.Add(New ReferenceSerializationAction(descriptor, Serializer))
                End If
            Next

            _PropertySerializationActions.TrimExcess()
        End If
    End Sub

    Public Sub New(ByVal parent As ISerializer, ByVal type As Type)
        Me.New(parent, type, True, True)
    End Sub

    Protected ReadOnly Property Serializer() As ISerializer
        Get
            Return _Serializer
        End Get
    End Property

    Protected ReadOnly Property CachedProperties() As Dictionary(Of String, PropertyDescriptor)
        Get
            Return _CachedProperites
        End Get
    End Property

    Public ReadOnly Property ReferenceType() As System.Type Implements IReferenceFormatter.ReferenceType
        Get
            Return _ReferenceType
        End Get
    End Property

#Region " Serialization Methods "

    Protected Overridable Sub WriteIdAttribute(ByVal graph As Object, ByVal writer As XmlWriter)
        Dim objectId As String

        objectId = Serializer.GetSerializeObjectId(graph).ToString

        writer.WriteAttributeString("__id", objectId)
    End Sub

    Protected Overridable Sub WriteTypeAttribute(ByVal graph As Object, ByVal writer As XmlWriter)
        WriteTypeAttribute(graph, _ReferenceType, writer)
    End Sub

    Protected Overridable Sub WriteTypeAttribute(ByVal graph As Object, ByVal type As Type, ByVal writer As XmlWriter)
        Dim typeName As String

        typeName = type.AssemblyQualifiedName

        writer.WriteAttributeString("__type", typeName)
    End Sub

    Protected Overridable Sub WriteValueAttribute(ByVal graph As Object, ByVal value As String, ByVal writer As XmlWriter)
        writer.WriteAttributeString("__value", value)
    End Sub

    Protected Overridable Sub SerializeProperties(ByVal graph As Object, ByVal writer As XmlWriter)
        For Each action In _PropertySerializationActions
            action.SerializeProperty(graph, writer)
        Next
    End Sub

    Public Overridable Sub Serialize(ByVal graph As Object, ByVal writer As System.Xml.XmlWriter) Implements IReferenceFormatter.Serialize
        writer.WriteStartElement("object")

        WriteIdAttribute(graph, writer)
        WriteTypeAttribute(graph, writer)

        SerializeProperties(graph, writer)

        writer.WriteEndElement()
    End Sub

#End Region

#Region " Deserialization Methods "

    Protected Overridable Function CreateObject(ByVal type As Type, ByVal reader As XmlReader) As Object
        Return Activator.CreateInstance(type)
    End Function

    Protected Overridable Sub DeserializeProperties(ByVal graph As Object, ByVal reader As XmlReader)
        If Not reader.MoveToNextAttribute Then Return ' No data attributes ...

        Do
            Dim propertyName As String = reader.Name
            Dim descriptor As PropertyDescriptor = Nothing

            _CachedProperites.TryGetValue(propertyName, descriptor)
            If descriptor Is Nothing Then Continue Do

            If Not descriptor.IsReadOnly Then
                If descriptor.IsValueType Then
                    Dim formatter As IValueFormatter = Serializer.GetValueFormatter(descriptor.PropertyType)
                    Dim value As Object = formatter.Deserialize(reader.Value)

                    descriptor.SetValue(graph, value)
                ElseIf descriptor.IsString Then
                    descriptor.SetValue(graph, reader.Value)
                Else
                    Dim ref As PropertyValueFixUp = New PropertyValueFixUp(Integer.Parse(reader.Value), descriptor, graph)
                    Serializer.RegisterPropertyValueFixUp(ref)
                End If
            ElseIf descriptor.IsIListDescendant Then
                Dim list As IList = DirectCast(descriptor.GetValue(graph), IList)

                If list Is Nothing Then Continue Do

                Dim ref As ReadOnlyListPropertyFixUp = New ReadOnlyListPropertyFixUp(Integer.Parse(reader.Value), list)
                Serializer.RegisterReadOnlyListPropertyFixUp(ref)
            End If
        Loop While reader.MoveToNextAttribute
    End Sub

    Protected Overridable Function GetObjectId(ByVal reader As XmlReader) As Integer
        Dim objectId As Integer = Integer.Parse(reader.Value)

        Return objectId
    End Function

    Protected Overridable Function GetObjectValue(ByVal reader As XmlReader) As String
        Dim value As String = reader.Value

        Return value
    End Function

    Protected Overridable Function GetObjectType(ByVal reader As XmlReader) As Type
        Dim typeName As String = reader.Value

        Return Type.GetType(typeName)
    End Function

    Public Overridable Function Deserialize(ByVal reader As System.Xml.XmlReader) As Object Implements IReferenceFormatter.Deserialize
        Dim objectId As Integer
        Dim type As Type
        Dim obj As Object

        reader.MoveToFirstAttribute() ' This is always __id
        objectId = GetObjectId(reader)

        reader.MoveToNextAttribute() ' This is always __type
        type = GetObjectType(reader)

        obj = CreateObject(type, reader)

        DeserializeProperties(obj, reader)

        Serializer.RegisterObject(objectId, obj)

        Return obj
    End Function
#End Region

End Class

Public Interface IPropertySerializationAction
    ReadOnly Property PropertyDescriptor() As PropertyDescriptor

    Sub SerializeProperty(ByVal graph As Object, ByVal writer As XmlWriter)
End Interface

Public Class ValueSerializationAction
    Implements IPropertySerializationAction

    Private _PropertyDescriptor As PropertyDescriptor
    Private _ValueFormatter As IValueFormatter

    Public Sub New(ByVal descriptor As PropertyDescriptor, ByVal formatter As IValueFormatter)
        _PropertyDescriptor = descriptor
        _ValueFormatter = formatter
    End Sub

    Public Sub SerializeProperty(ByVal graph As Object, ByVal writer As System.Xml.XmlWriter) Implements IPropertySerializationAction.SerializeProperty
        Dim serializedValue As String
        Dim propertyValue As Object

        propertyValue = PropertyDescriptor.GetValue(graph)
        serializedValue = _ValueFormatter.Serialize(propertyValue)

        writer.WriteAttributeString(PropertyDescriptor.PropertyName, serializedValue)
    End Sub

    Public ReadOnly Property PropertyDescriptor() As PropertyDescriptor Implements IPropertySerializationAction.PropertyDescriptor
        Get
            Return _PropertyDescriptor
        End Get
    End Property
End Class

Public Class StringSerializationAction
    Implements IPropertySerializationAction

    Private _PropertyDescriptor As PropertyDescriptor

    Public Sub New(ByVal descriptor As PropertyDescriptor)
        _PropertyDescriptor = descriptor
    End Sub

    Public Sub SerializeProperty(ByVal graph As Object, ByVal writer As System.Xml.XmlWriter) Implements IPropertySerializationAction.SerializeProperty
        Dim serializedValue As String

        serializedValue = DirectCast(PropertyDescriptor.GetValue(graph), String)

        writer.WriteAttributeString(PropertyDescriptor.PropertyName, serializedValue)
    End Sub

    Public ReadOnly Property PropertyDescriptor() As PropertyDescriptor Implements IPropertySerializationAction.PropertyDescriptor
        Get
            Return _PropertyDescriptor
        End Get
    End Property
End Class

Public Class ReferenceSerializationAction
    Implements IPropertySerializationAction

    Private _PropertyDescriptor As PropertyDescriptor
    Private _Serializer As ISerializer

    Public Sub New(ByVal descriptor As PropertyDescriptor, ByVal formatter As ISerializer)
        _PropertyDescriptor = descriptor
        _Serializer = formatter
    End Sub

    Public ReadOnly Property PropertyDescriptor() As PropertyDescriptor Implements IPropertySerializationAction.PropertyDescriptor
        Get
            Return _PropertyDescriptor
        End Get
    End Property

    Public Sub SerializeProperty(ByVal graph As Object, ByVal writer As System.Xml.XmlWriter) Implements IPropertySerializationAction.SerializeProperty
        Dim objectId As String
        Dim propertyValue As Object

        propertyValue = PropertyDescriptor.GetValue(graph)
        objectId = _Serializer.GetSerializeObjectId(propertyValue).ToString

        writer.WriteAttributeString(PropertyDescriptor.PropertyName, objectId)
    End Sub
End Class